package com.erbao.nettystudy.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 单线程模型：
 * Java 网络一般用netty，netty对nio做了封装，用起来更方便
 * nio 两种变成模型：用一个线程管所有的事；用一个线程+一个线程池管所有的事
 * netty 基本上用两个线程池。
 * 除非客户端暴多，一般一个线程就够了
 * 所有的客户端都去连接 大管家,大管家 轮循 看谁那有事了
 * @author CaoBaoYin
 * @since 2021/10/21 20:24
 */
public class Server {
    public static void main(String[] args) throws IOException {
        // 全双工，读写可同时
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
        ssc.configureBlocking(false);//默认阻塞，设为非阻塞形式

        System.out.println("server started, listening on ...");

        Selector selector = Selector.open(); // selector就是一个大管家，轮循速度特别快
        ssc.register(selector, SelectionKey.OP_ACCEPT);// 只关注客户端连接。关注的事件，四种常量

        while (true) {
            selector.select();// 阻塞方法，监听的channel上有任何一个channel，有关注的事件发生时就会返回。事件是存储在了selection key中
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();// 处理之前去掉，避免下次取到重复的事件。不能处理后再remove，因为处理过程需要时间
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) {
        // 申请连接，判断是否可接收，可连接。然后把客人接进屋里。也会产生新的channel和客户端相连接
        if (key.isAcceptable()) {
            ServerSocketChannel scc = (ServerSocketChannel) key.channel();
            SocketChannel sc = null;
            try {
                sc = scc.accept();
                sc.configureBlocking(false);
                sc.register(key.selector(), SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }

        } else if (key.isReadable()) {
            SocketChannel sc = null;
            try {
                sc = (SocketChannel) key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                int len = sc.read(byteBuffer);

                if (len != -1) {
                    System.out.println(new String(byteBuffer.array(), 0 , len));
                }

                ByteBuffer wrap = ByteBuffer.wrap("Client back msg...".getBytes());
                sc.write(wrap);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (sc != null) {
                    try {
                        sc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
